/*
 * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package org.eclipse.imagen.tilecodec;

import java.awt.image.SampleModel;
import org.eclipse.imagen.ParameterListDescriptor;
import org.eclipse.imagen.ParameterListDescriptorImpl;
import org.eclipse.imagen.util.Range;

/**
 * This class is the descriptor for the "JPEG" tile codec. "jpeg" is a lossy tile codec, which involves compressing the
 * tile data using the jpeg standard. The format name for the jpeg tile codec is "jpeg". The encoded stream contains the
 * <code>SampleModel</code> and the tile's upper left corner position, thus the <code>includesSampleModelInfo()</code>
 * and <code>includesLocationInfo()</code> methods in this descriptor return true.
 *
 * <p>This JPEG tile codec works well only on byte type images with 1, 3 or 4 bands.
 *
 * <p>While both the "tileDecoder" and "tileEncoder" registry modes for the "jpeg" tile codec scheme have the same set
 * of parameters, the parameters for the "tileDecoder" mode are read-only and will be ignored if values are set for them
 * in the <code>TileCodecParameterList</code> passed to the <code>TileDecoder</code>. The reason for this is that the
 * parameter values needed to control the decoding process are included in the encoded stream, therefore parameter
 * values specified externally are ignored. Once the decoding is completed, the parameter values in the <code>
 * TileCodecParameterList</code> are updated to reflect the values specified in the encoded stream.
 *
 * <p>
 *
 * <table border=1>
 * <caption>Resource List</caption>
 * <tr><th>Name</th>        <th>Value</th></tr>
 * <tr><td>Vendor</td>      <td>org.eclipse.imagen.media</td></tr>
 * <tr><td>Description</td> <td>A descriptor to describe the lossy "jpeg" codec
 *                          scheme. </td></tr>
 * <tr><td>DocURL</td>      <td>http://java.sun.com/products/java-media/jai/forDevelopers/jai-apidocs/javax/media/jai/tilecodec/JPEGTileCodecDescriptor.html</td></tr>
 * <tr><td>Version</td>     <td>1.2</td></tr>
 * <tr><td>quality</td>    <td>
 *                           <p>A factor that relates to the desired tradeoff
 * 		     between image quality and the image data
 * 		     compression ratio. The range of this parameter
 * 		     is from 0.0 to 1.0. A setting of 1.0 produces the
 * 		     highest quality image at a lower compression
 * 		     rate. A setting of 0.0 produces the highest
 * 		     compression ratio, with a sacrifice to image
 * 		     quality. The default value is 0.75. <p>In JPEG,
 * 		     compression, there are two ways to control the
 * 		     quantization quality: one is define quantization
 * 		     table for each image component; the other is
 * 		     define the quality. The later overrides the
 * 		     former. If neither the quality nor quantization
 * 		     tables are set, the default setting is used.
 *                           <p>When the quality is set, a group of
 * 		     quantization tables are generated by rescaling
 * 		     the default quantization tables. For more
 * 		     infomation, please refer to the links below.
 *                           </td></tr>
 * <tr><td>qualitySet</td>   <td>A boolean used to indicate that the
 * 			     parameter <code>quality</code> is set or
 * 		     not.</td></tr>
 * <tr><td>horizontalSubsampling</td>    <td>The subsampling rate in the
 * 			horizontal direction applied to each image
 * 			component to reduce their resolution
 * 			prior to encoding.</td></tr>
 * <tr><td>verticalSubsampling</td>    <td>The subsampling rate in the
 *                              vertical direction applied to each image
 *                              component to reduce their resolution
 *                              prior to encoding.</td></tr>
 * <tr><td>quantizationTableMapping</td>    <td>In JPEG compression, several
 * 			image components may share one quantization
 * 			table. This is the mapping between the
 * 			image component and the quantization
 * 			tables.</td></tr>
 * <tr><td>quantizationTable0</td>    <td>A quantization table for JPEG codec
 * 			is an array of 64 (8x8) expressed in zig-zag
 * 			order (see the JPEG spec section K.1). Since
 *                              this descriptor defines a JPEG scheme where the
 *                              tile has at most 4 components, so at most 4
 *                              quantization tables will
 * 			be used. This parameter is the first
 * 			quantization table. </td></tr>
 * <tr><td>quantizationTable1</td> <td>The second quantization table.</td></tr>
 * <tr><td>quantizationTable2</td> <td>The third quantization table.</td></tr>
 * <tr><td>quantizationTable3</td> <td>The fourth quantization table.</td></tr>
 * <tr><td>restartInterval</td>  <td>JPEG images use restart markers to define
 * 			multiple strips or tiles. The restart markers
 * 			are inserted periodically into the image data
 * 			to delineate image segments known as restart
 * 		        intervals. To limit the effect of bitstream
 * 			errors to a single restart interval, JAI
 * 			provides methods to set the restart interval
 * 			in JPEG Minimum Coded Units (MCUs).
 * 			The default is zero (no restart interval
 * 			markers). </td></tr>
 * <tr><td>writeImageInfo</td>    <td>A boolean instructs the encoder to
 * 			write the image data to the output
 * 			stream. </td></tr>
 * <tr><td>writeTableInfo</td>    <td>A boolean instructs the encoder to
 * 			write the table data to the output
 * 			stream.</td></tr>
 * <tr><td>writeJFIFHeader</td>    <td>The JPEG File Interchange Format (JFIF)
 * 			is a minimal file format that enables JPEG
 * 			bitstreams to be exchanged between a wide
 * 			variety of platforms and applications.
 * 			The parameter instructs the encoder to write
 * 			the output stream in the JFIF format.</td></tr>
 * </table>
 *
 * <p>
 *
 * <table border=1>
 * <caption>Parameter List</caption>
 * <tr><th>Name</th>          <th>Class Type</th>
 *                            <th>Default Value</th></tr>
 * <tr><td>quality</td>       <td>java.lang.Float</td>
 *                            <td>0.75</td>
 * <tr><td>qualitySet</td>    <td>java.lang.Boolean</td>
 *                            <td>true</td>
 * <tr><td>horizontalSubsampling</td>          <td>integer array</td>
 *                            <td>{1,1,1}</td>
 * <tr><td>verticalSubsampling</td>          <td>integer array</td>
 *                            <td>{1,1,1}</td>
 * <tr><td>quantizationTableMapping</td>          <td>integer array</td>
 *                            <td>{0,1,1}</td>
 * <tr><td>quantizationTable0</td>          <td>integer array</td>
 *                            <td>The default Luminance table as defined in
 *                                section K.1 of the JPEG specification.</td>
 * <tr><td>quantizationTable1</td>          <td>integer array</td>
 *                            <td>The default Chrominance table as defined in
 *                                section K.1 of the JPEG specification.</td>
 * <tr><td>quantizationTable2</td>          <td>integer array</td>
 *                            <td>The default Chrominance table as defined in
 *                                section K.1 of the JPEG specification.</td>
 * <tr><td>quantizationTable3</td>          <td>integer array</td>
 *                            <td>The default Chrominance table as defined in
 *                                section K.1 of the JPEG specification.</td>
 * <tr><td>restartInterval</td>          <td>java.lang.Integer</td>
 *                            <td>0</td>
 * <tr><td>writeImageInfo</td>          <td>java.lang.Boolean</td>
 *                            <td>true</td>
 * <tr><td>writeTableInfo</td>          <td>java.lang.Boolean</td>
 *                            <td>true</td>
 * <tr><td>writeJFIFHeader</td>          <td>java.lang.Boolean</td>
 *                            <td>false</td>
 * </table>
 *
 * @see com.sun.image.codec.jpeg.JPEGQTable
 * @see com.sun.image.codec.jpeg.JPEGDecodeParam
 * @see com.sun.image.codec.jpeg.JPEGEncodeParam
 * @since JAI 1.1
 */
public class JPEGTileCodecDescriptor extends TileCodecDescriptorImpl {

    private static final int[] lumQuantizationTable = {
        16, 11, 12, 14, 12, 10, 16, 14,
        13, 14, 18, 17, 16, 19, 24, 40,
        26, 24, 22, 22, 24, 49, 35, 37,
        29, 40, 58, 51, 61, 60, 57, 51,
        56, 55, 64, 72, 92, 78, 64, 68,
        87, 69, 55, 56, 80, 109, 81, 87,
        95, 98, 103, 104, 103, 62, 77, 113,
        121, 112, 100, 120, 92, 101, 103, 99
    };

    private static final int[] chromQuantizationTable = {
        17, 18, 18, 24, 21, 24, 47, 26,
        26, 47, 99, 66, 56, 66, 99, 99,
        99, 99, 99, 99, 99, 99, 99, 99,
        99, 99, 99, 99, 99, 99, 99, 99,
        99, 99, 99, 99, 99, 99, 99, 99,
        99, 99, 99, 99, 99, 99, 99, 99,
        99, 99, 99, 99, 99, 99, 99, 99,
        99, 99, 99, 99, 99, 99, 99, 99
    };

    // Parameter names
    private static final String[] paramNames = {
        "quality",
        "qualitySet",
        "horizontalSubsampling",
        "verticalSubsampling",
        "quantizationTableMapping",
        "quantizationTable0",
        "quantizationTable1",
        "quantizationTable2",
        "quantizationTable3",
        "restartInterval",
        "writeImageInfo",
        "writeTableInfo",
        "writeJFIFHeader"
    };

    // Parameter class names
    private static final Class[] paramClasses = {
        java.lang.Float.class,
        java.lang.Boolean.class,
        int[].class,
        int[].class,
        int[].class,
        int[].class,
        int[].class,
        int[].class,
        int[].class,
        java.lang.Integer.class,
        java.lang.Boolean.class,
        java.lang.Boolean.class,
        java.lang.Boolean.class
    };

    // Parameter default values.
    private static final Object[] paramDefaults = {
        new Float(0.75f),
        new Boolean(true),
        new int[] {1, 1, 1},
        new int[] {1, 1, 1},
        new int[] {0, 1, 1},
        lumQuantizationTable,
        chromQuantizationTable,
        chromQuantizationTable,
        chromQuantizationTable,
        new Integer(0),
        new Boolean(true),
        new Boolean(true),
        new Boolean(false)
    };

    /* XXX: If Set can be specified for non EnumeratedParameter types.
       // Valid values for the boolean valued parameters
       private static java.util.Set set =
           Collections.synchronizedSet(new HashSet());

       static {
    set.add(Boolean.TRUE);
    set.add(Boolean.FALSE);
       }
       */

    // Parameters' valid value ranges
    private static final Object[] validParamValues = {
        new Range(java.lang.Float.class, new Float(0.0f), new Float(1.0f)),
        null, // set,
        null,
        null,
        null,
        null,
        null,
        null,
        null,
        new Range(java.lang.Integer.class, new Integer(0), null),
        null, // set,
        null, // set,
        null // set
    };

    private static ParameterListDescriptor paramListDescriptor =
            new ParameterListDescriptorImpl(null, paramNames, paramClasses, paramDefaults, validParamValues);

    /** Creates a <code>JPEGTileCodecDescriptor</code> */
    public JPEGTileCodecDescriptor() {
        super("jpeg", true, true);
    }

    /**
     * Returns a <code>TileCodecParameterList</code> valid for the specified modeName and compatible with the supplied
     * <code>TileCodecParameterList</code>. For example, given a <code>TileCodecParameterList</code> used to encode a
     * tile with the modeName being specified as "tileDecoder", this method will return a <code>TileCodecParameterList
     * </code> sufficient to decode that same tile.
     *
     * <p>If the supplied modeName is one of the valid mode names as ascertained from the <code>getSupportedNames()
     * </code> method, this method returns a <code>TileCodecParameterList</code> that contains values that are
     * compatible for the supplied mode name.
     *
     * @param modeName The registry mode to return a valid parameter list for.
     * @param otherParamList The parameter list for which a compatible parameter list for the complementary modeName is
     *     to be found.
     * @throws IllegalArgumentException if <code>modeName</code> is null.
     * @throws IllegalArgumentException if <code>modeName</code> is not one of the modes valid for this descriptor, i.e
     *     those returned from the getSupportedNames() method.
     * @throws IllegalArgumentException if <code>otherParamList</code> is null.
     */
    public TileCodecParameterList getCompatibleParameters(String modeName, TileCodecParameterList otherParamList) {
        if (modeName == null) {
            throw new IllegalArgumentException(JaiI18N.getString("TileCodecDescriptorImpl1"));
        }

        if (otherParamList == null) {
            throw new IllegalArgumentException(JaiI18N.getString("TileCodecDescriptorImpl3"));
        }

        String name = getName();
        if (!otherParamList.getFormatName().equals(name)) {
            throw new IllegalArgumentException(JaiI18N.getString("TileCodec2"));
        }

        if (otherParamList.isValidForMode(modeName)) return otherParamList;

        if (modeName.equalsIgnoreCase("tileDecoder")) {
            return new TileCodecParameterList(
                    name, new String[] {"tileDecoder"}, otherParamList.getParameterListDescriptor());
        } else if (modeName.equalsIgnoreCase("tileEncoder")) {
            return new TileCodecParameterList(
                    name, new String[] {"tileEncoder"}, otherParamList.getParameterListDescriptor());
        } else {
            throw new IllegalArgumentException(JaiI18N.getString("TileCodec1"));
        }
    }

    /**
     * Returns the default parameters for the specified modeName as an instance of the <code>TileCodecParameterList
     * </code>. If the supplied modeName is one of the valid mode names as ascertained from the <code>
     * getSupportedNames()</code> method, this method returns the default parameters for that mode.
     *
     * @param modeName The mode to return the default parameters for.
     * @throws IllegalArgumentException if <code>modeName</code> is null.
     * @throws IllegalArgumentException if <code>modeName</code> is not one of the modes valid for this descriptor, i.e
     *     those returned from the getSupportedNames() method.
     */
    public TileCodecParameterList getDefaultParameters(String modeName) {
        if (modeName == null) throw new IllegalArgumentException(JaiI18N.getString("TileCodecDescriptorImpl1"));

        String validNames[] = getSupportedModes();
        boolean valid = false;

        for (int i = 0; i < validNames.length; i++) {
            if (modeName.equalsIgnoreCase(validNames[i])) {
                valid = true;
                break;
            }
        }

        if (valid == false) {
            throw new IllegalArgumentException(JaiI18N.getString("TileCodec1"));
        }

        return new TileCodecParameterList("jpeg", new String[] {"tileDecoder", "tileEncoder"}, paramListDescriptor);
    }

    /**
     * Returns the default parameters for the specified modeName as an instance of the <code>TileCodecParameterList
     * </code>, adding a "sampleModel" parameter with the specified value to the parameter list. If the supplied
     * modeName is one of the valid mode names as ascertained from the <code>getSupportedNames()</code> method, this
     * method returns the default parameters for that mode.
     *
     * <p>This method should be used when includesSampleModelInfo() returns false. If includesSampleModelInfo() returns
     * true, the supplied <code>SampleModel</code> is ignored.
     *
     * <p>For the JPEG codec, includesSampleModelInfo() returns true, so the supplied <code> SampleModel</code> is
     * ignored.
     *
     * @param modeName The mode to return the default parameters for.
     * @param sm The <code>SampleModel</code> used to create the default decoding parameter list.
     * @throws IllegalArgumentException if <code>modeName</code> is null.
     * @throws IllegalArgumentException if <code>modeName</code> is not one of the modes valid for this descriptor, i.e
     *     those returned from the getSupportedNames() method.
     */
    public TileCodecParameterList getDefaultParameters(String modeName, SampleModel sm) {
        return getDefaultParameters(modeName);
    }

    /**
     * Returns the <code>ParameterListDescriptor</code> that describes the associated parameters (NOT sources). If the
     * supplied modeName is one of the valid mode names as ascertained from the <code>getSupportedNames()</code> method,
     * this method returns a non-null <code>ParameterListDescriptor</code> with the appropriate parameters.
     *
     * @param modeName The mode to return the ParameterListDescriptor for.
     * @throws IllegalArgumentException if <code>modeName</code> is null.
     * @throws IllegalArgumentException if <code>modeName</code> is not one of the modes valid for this descriptor, i.e
     *     those returned from the getSupportedNames() method.
     */
    public ParameterListDescriptor getParameterListDescriptor(String modeName) {
        if (modeName == null) throw new IllegalArgumentException(JaiI18N.getString("TileCodecDescriptorImpl1"));

        String validNames[] = getSupportedModes();
        boolean valid = false;

        for (int i = 0; i < validNames.length; i++) {
            if (modeName.equalsIgnoreCase(validNames[i])) {
                valid = true;
                break;
            }
        }

        if (valid == false) {
            throw new IllegalArgumentException(JaiI18N.getString("TileCodec1"));
        }

        return paramListDescriptor;
    }
}
